// ==UserScript== // @name Via Css隐藏规则日志 // @namespace https://viayoo.com/ // @version 2.3.6 // @license MIT // @description 格式化CSS规则,检测哪些规则生效,并输出匹配日志。 // @author Copilot & Grok // @run-at document-start // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @require https://cdn.jsdelivr.net/npm/js-beautify@1.14.0/js/lib/beautify-css.js // ==/UserScript== (function() { 'use strict'; const BUTTON_STORAGE = { ENABLED: 'floatingButtonEnabled', LEFT: 'floatingButtonLeft', TOP: 'floatingButtonTop' }; const CSS_FILE_PATH = '/via_inject_blocker.css'; const LONG_PRESS_THRESHOLD = 500; // 长按阈值,单位:毫秒 const log = (msg, type = 'log') => console[type](`[Via CSS Logger] ${msg}`); const createStyledElement = (tag, styles, text) => { const el = document.createElement(tag); Object.assign(el.style, styles); if (text) el.textContent = text; return el; }; const formatCss = rawCss => { try { return css_beautify(rawCss, { indent_size: 2, selector_separator_newline: true }); } catch (e) { log(`CSS格式化失败:${e.message}`, 'error'); return null; } }; // 改进后的选择器拆分函数,支持括号嵌套 const splitSelectors = selectorText => { const selectors = []; let current = '', inAttr = false, quote = null, depth = 0; // 用于跟踪括号嵌套深度 for (let i = 0; i < selectorText.length; i++) { const char = selectorText[i]; if (inAttr) { current += char; if (char === quote) inAttr = false; } else if (char === '"' || char === "'") { inAttr = true; quote = char; current += char; } else if (char === '(') { depth++; current += char; } else if (char === ')') { depth--; current += char; } else if (char === ',' && !inAttr && depth === 0) { if (current.trim()) selectors.push(current.trim()); current = ''; } else { current += char; } } if (current.trim()) selectors.push(current.trim()); return selectors; }; const extractValidSelectors = rule => { if (!rule.selectorText) return []; return splitSelectors(rule.selectorText).filter(selector => { try { document.querySelector(selector); return true; } catch { return false; } }); }; const checkActiveRules = sheet => { if (!sheet?.cssRules) return []; const activeRules = []; for (const rule of sheet.cssRules) { if (rule.selectorText) { extractValidSelectors(rule).forEach(selector => { const elements = document.querySelectorAll(selector); if (elements.length) { activeRules.push({ selector, count: elements.length }); } }); } } return activeRules; }; const checkCssFile = async () => { const cssUrl = `https://${window.location.hostname}${CSS_FILE_PATH}`; try { const response = await fetch(cssUrl); if (!response.ok) throw new Error(`状态码: ${response.status}`); const rawCss = await response.text(); if (!rawCss.trim()) throw new Error('CSS文件为空'); const formattedCss = formatCss(rawCss); if (!formattedCss) throw new Error('CSS格式化失败'); const style = createStyledElement('style'); style.textContent = formattedCss; document.head.appendChild(style); const activeRules = checkActiveRules(style.sheet); document.head.removeChild(style); const message = activeRules.length ? `检测完成!共有 ${activeRules.length} 条规则生效:\n\n` + activeRules.map((r, i) => `${i + 1}. 规则: ${window.location.hostname}##${r.selector.slice(0, 750)}\n 匹配数: ${r.count}\n`).join('\n') : '没有发现生效的CSS规则!'; alert(message); } catch (e) { log(`CSS检查失败:${e.message}`, 'error'); alert(`检查CSS文件失败:${e.message}\nURL: ${cssUrl}`); } }; const createFloatingButton = () => { if (window.self !== window.top) { log('当前页面是iframe,跳过按钮创建', 'warn'); return; } const button = createStyledElement('div', { position: 'fixed', zIndex: '10000', width: '70px', height: '35px', backgroundColor: '#2d89ef', color: 'white', borderRadius: '5px', textAlign: 'center', lineHeight: '35px', fontSize: '14px', boxShadow: '0 4px 6px rgba(0,0,0,0.1)', cursor: 'pointer', opacity: '0.9', transition: 'opacity 0.3s, transform 0.3s', touchAction: 'none' }, 'CSS日志'); const defaultLeft = window.innerWidth - 100; const defaultTop = window.innerHeight - 100; button.style.left = `${GM_getValue(BUTTON_STORAGE.LEFT, defaultLeft)}px`; button.style.top = `${GM_getValue(BUTTON_STORAGE.TOP, defaultTop)}px`; document.body.appendChild(button); log('悬浮按钮已创建'); let isDragging = false, startX, startY, startLeft, startTop, touchStartTime; button.addEventListener('touchstart', e => { e.preventDefault(); touchStartTime = Date.now(); isDragging = false; const touch = e.touches[0]; startX = touch.clientX; startY = touch.clientY; startLeft = parseInt(button.style.left, 10) || 0; startTop = parseInt(button.style.top, 10) || 0; }); button.addEventListener('touchmove', e => { e.preventDefault(); const touch = e.touches[0]; const deltaX = touch.clientX - startX; const deltaY = touch.clientY - startY; if (Date.now() - touchStartTime >= LONG_PRESS_THRESHOLD) { isDragging = true; const newLeft = startLeft + deltaX; const newTop = startTop + deltaY; const rect = button.getBoundingClientRect(); button.style.left = `${Math.max(0, Math.min(newLeft, window.innerWidth - rect.width))}px`; button.style.top = `${Math.max(0, Math.min(newTop, window.innerHeight - rect.height))}px`; } }); button.addEventListener('touchend', e => { e.preventDefault(); const touchDuration = Date.now() - touchStartTime; if (isDragging && touchDuration >= LONG_PRESS_THRESHOLD) { const rect = button.getBoundingClientRect(); const newLeft = rect.left + rect.width / 2 < window.innerWidth / 2 ? 0 : window.innerWidth - rect.width; button.style.left = `${newLeft}px`; GM_setValue(BUTTON_STORAGE.LEFT, newLeft); GM_setValue(BUTTON_STORAGE.TOP, parseInt(button.style.top, 10)); } else if (touchDuration < LONG_PRESS_THRESHOLD) { checkCssFile(); } }); return button; }; const ensureButtonExists = () => { if (!document.querySelector("div[style*='CSS日志']")) { log('创建悬浮按钮'); createFloatingButton(); } else { log('悬浮按钮已存在'); } }; const resetButtonPosition = () => { const defaultLeft = window.innerWidth - 100; const defaultTop = window.innerHeight - 100; GM_setValue(BUTTON_STORAGE.LEFT, defaultLeft); GM_setValue(BUTTON_STORAGE.TOP, defaultTop); const button = document.querySelector("div[style*='CSS日志']"); if (button) { button.style.left = `${defaultLeft}px`; button.style.top = `${defaultTop}px`; } alert('悬浮按钮位置已重置至默认位置!'); }; const init = () => { const isButtonEnabled = GM_getValue(BUTTON_STORAGE.ENABLED, false); GM_registerMenuCommand( isButtonEnabled ? '关闭悬浮按钮' : '开启悬浮按钮', () => { GM_setValue(BUTTON_STORAGE.ENABLED, !isButtonEnabled); alert(`悬浮按钮已${isButtonEnabled ? '关闭' : '开启'}!`); location.reload(); } ); GM_registerMenuCommand('检测CSS隐藏规则', checkCssFile); GM_registerMenuCommand('重置悬浮按钮位置', resetButtonPosition); if (isButtonEnabled) { document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', ensureButtonExists) : ensureButtonExists(); } }; init(); })();